﻿// -----------------------------------------------------------------------
// <copyright file="Player.cs" company="The BomberJoe Team">
// Author: Oliver Phillip Roer <olpr@itu.dk>
// </copyright>
// -----------------------------------------------------------------------

namespace Bomberman
{
    using System;
    using System.Diagnostics.Contracts;

    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;

    /// <summary>
    /// A game object that reacts directly on input. Is a GAME_OBJECT.
    /// </summary>
    public class Player : GameObject
    {
        private const float MaxMove = 0.15f;

        // The players controls.
        private readonly ControlScheme _controls;        

        // The player number.
        private readonly Profile _playerProfile;

        // The players movement speed.
        private float _moveSpeed = 0.075f;

        // The current amount of active bombs owned by this player.
        private int _activeBombs;

        // The maximum amount of active bombs a player can have on the field.
        private uint _activeBombLimit;

        // The range of squares that bombs placed by this player can cover.
        private uint _bombFireRange;

        // The players offset from the center of the current Bottom he's positioned at (0.0-1.0)
        private float _offset;

        // The direction the player is facing.
        private Direction _faceDirection;

        /// <summary>
        /// Initializes a new instance of the <see cref="Player"/> class. 
        /// </summary>
        /// <param name="texture">
        /// The texture which the player should use to display itself.
        /// </param>
        /// <param name="playerProfile">
        /// The player Profile.
        /// </param>
        /// <param name="gridVector">
        /// A vector that describes where on the stage the player is positioned.
        /// </param>
        public Player(Texture2D texture, Profile playerProfile, IntVector gridVector = default(IntVector))
            : base(texture, true, gridVector)
        {
            _playerProfile = playerProfile;
            _controls = ControlScheme.GetControlScheme(_playerProfile.PlayerNumber);
            Initialize();
        }

        public static SoundEffect DieSFX { private get; set; }

        public uint Deaths 
        {
            get
            {
                return _playerProfile.Score.Deaths;
            }

            private set
            {
                _playerProfile.Score.Deaths = value;
            } 
        }

        public int Kills 
        { 
            get
            {
                return _playerProfile.Score.Kills;
            }

            set
            {
                _playerProfile.Score.Kills = value;
            } 
        }

        /// <summary>
        /// Gets a value indicating whether the player is alive or not.
        /// </summary>
        /// <value>
        /// Indicates whether the player is alive or not.
        /// </value>
        public bool Active { get; private set; }

        protected override Rectangle PixelRect
        {
            get
            {
                float x = GridVector.X;
                float y = GridVector.Y;

                if (_faceDirection.Axis == Axis.X)
                {
                    x += _offset;
                }
                else
                {
                    y += _offset;
                }

                return new Rectangle((int)(x * Scale), (int)(y * Scale), Scale, Scale);
            }
        }

        /// <summary>
        /// Gets the bottom at which the player is standing.
        /// </summary>
        private Bottom CurrentBottom
        {
            get
            {
                return Stage.CurrentStage.GetBottom(GridVector);
            }
        }

        /// <summary>
        /// Damages the player (Currently kills him in one hit). 
        /// </summary>
        /// <param name="damageDealer">
        /// The damage Dealer.
        /// </param>
        public override void Damage(Player damageDealer)
        {
            Contract.Ensures(!Active);
            if (Active)
            {
                if (damageDealer != null & damageDealer != this)
                {
                    damageDealer.Kills++;
                }

                DieSFX.Play();
                Deaths++;
            }

            this.Active = false;
        }

        public override void Update(GameTime gameTime)
        {
            // Do not do anything if the player is dead..
            if (!Active)
            {
                return;
            }

            // React on movement keys.
            if (Keyboard.GetState().IsKeyDown(_controls.Up))
            {
                Move(Direction.North);
            }
            else if (Keyboard.GetState().IsKeyDown(_controls.Down))
            {
                Move(Direction.South);
            }
            else if (Keyboard.GetState().IsKeyDown(_controls.Left))
            {
                Move(Direction.West);
            }
            else if (Keyboard.GetState().IsKeyDown(_controls.Right))
            {
                Move(Direction.East);
            }

            // React on bomb key.
            if (Keyboard.GetState().IsKeyDown(_controls.Bomb))
            {
                PlaceBomb();
            }
        }

        /// <summary>
        /// Draw the player.
        /// </summary>
        /// <param name="batch">
        /// The SpriteBatch that should take care of drawing the player.
        /// </param>
        public override void Draw(SpriteBatch batch)
        {
            // Do not draw the player if he is dead.
            if (Active)
            {
                base.Draw(batch);
            }
        }

        /// <summary>
        /// Adds the effect of a PowerUp to the player.
        /// </summary>
        /// <param name="powerUp">The PowerUp which effects should be added to the player.</param>
        public void AddPowerUp(PowerUp powerUp)
        {
            Contract.Requires(powerUp != null);
            _activeBombLimit += powerUp.Bombs;
            _bombFireRange += powerUp.Fire;
            _moveSpeed += powerUp.Move;
            if (_moveSpeed > MaxMove)
            {
                _moveSpeed = MaxMove;
            }
        }

        /// <summary>
        /// Decreases the count of how many active bombs the player have on stage.
        /// Should only be called when one of the players bombs have exploded and therefore is unactive.
        /// </summary>
        public void BombInactive()
        {
            _activeBombs--;
        }

        /// <summary>
        /// Attempts to move the player.
        /// </summary>
        /// <param name="moveDirection">The direction which the player should move.</param>
        private void Move(Direction moveDirection)
        {
            // If we're trying to walk into an impassable block on the opposite axis...
            if (!CurrentBottom.GetNeighbour(moveDirection).Passable && _faceDirection.Axis != moveDirection.Axis)
            {
                // ...we'll do nothing
                return;
            }

            // If we're trying to move along the same axis...
            if (_faceDirection.Axis == moveDirection.Axis)
            {
                // ...we'll move the offset in the right direction on that axis.
                _offset += this._moveSpeed * moveDirection.GetAxisSign();

                // If we cannot move into the next block in that direction
                // and the new offset will cause us to move too far in that direction...
                if (!CurrentBottom.GetNeighbour(moveDirection).Passable && _offset * moveDirection.GetAxisSign() > 0)
                {
                    // ...we'll back off to the center and face the direction we just moved.
                    _offset = 0.0f;
                    _faceDirection = moveDirection;
                }
            }
            else 
            {
                // Else, we're trying to move along the opposite axis. (into a passable block)

                // If the movement speed it greater than the absolute value of the offset...
                if (this._moveSpeed > Math.Abs(_offset))
                {
                    // We are able to go to the center, face the direction we should move, and move a bit further in that direction.                     
                    _faceDirection = moveDirection;
                    _offset = (this._moveSpeed - Math.Abs(_offset)) * _faceDirection.GetAxisSign();
                }
                else
                {
                    // Else, we can at least move towards the center.

                    // If we're not facing the right direction...
                    if (_faceDirection.GetAxisSign() * _offset < 0)
                    {
                        // We'll turn the opposite direction.
                        _faceDirection = Direction.Opposite(_faceDirection);
                    }

                    _offset = (Math.Abs(_offset) - this._moveSpeed) * _faceDirection.GetAxisSign();
                }
            }

            // At this point, the player has moved, and the offset should be recalibrated in case it's gotten outside its boundaries.
            CalibrateOffset(moveDirection);
        }

        private void CalibrateOffset(Direction direction)
        {
            // Since the players movements speed is constant and below 0.5f, we know that he can't move two tiles in one update.
            if (_offset > -0.5f && _offset < 0.5f)
            {
                return;
            }

            CurrentBottom.GetNeighbour(direction).Enter(this);
            GridVector += direction.Vector;
            _offset -= direction.GetAxisSign();
        }

        /// <summary>
        /// Initializes the player.
        /// </summary>
        private void Initialize()
        {
            _bombFireRange = 2;
            _activeBombLimit = 1;
            _activeBombs = 0;
            _offset = 0.0f;
            this._faceDirection = Direction.South;
            Active = true;
        }

        private void PlaceBomb()
        {
            if (CurrentBottom.Top != null || _activeBombs >= this._activeBombLimit)
            {
                return;
            }

            _activeBombs++;
            Stage.CurrentStage.AddTop(new Bomb(_bombFireRange, this), GridVector);
        }        
    }
}
